package cn.infomany.util;

import lombok.extern.slf4j.Slf4j;

import java.io.Serializable;
import java.lang.invoke.SerializedLambda;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import static org.apache.commons.lang3.StringUtils.substringAfter;

/**
 * 字段工具,可以通过Lambda表达式获取字段名称
 *
 * @author zjb
 */
@Slf4j
public class FieldUtils {


    /**
     * getter方法接口定义
     */
    @FunctionalInterface
    public interface IGetter<T> extends Serializable {
        Object apply(T source);
    }

    /**
     * 缓存类-Lambda的映射关系
     */
    private static final Map<Class<?>, SerializedLambda> CLASS_LAMDBA_CACHE = new ConcurrentHashMap<>();
    private final static String GET_METHOD = "get";
    private final static String SET_METHOD = "set";
    private final static String IS_METHOD = "is";

    /***
     * 转换方法引用为属性名
     * @param fn
     * @return
     */
    public static <T> String getFieldName(IGetter<T> fn) {
        SerializedLambda lambda = getSerializedLambda(fn);
        String methodName = lambda.getImplMethodName();
        String prefix = null;

        // 找到对应的前缀
        String[] prefixes = new String[]{GET_METHOD, IS_METHOD, SET_METHOD};
        for (String item : prefixes) {
            if (methodName.startsWith(item)) {
                prefix = item;
                break;
            }
        }

        if (prefix == null) {
            log.warn("无效的getter方法: " + methodName);
        }
        return uncapFirst(substringAfter(methodName, prefix));
    }

    // 将首字母小写
    private static String uncapFirst(String name) {
        String firstChar = name.substring(0, 1);
        String firstLowerCase = firstChar.toLowerCase();
        return firstLowerCase + name.substring(1);
    }

    /***
     * 获取类对应的Lambda
     * @param fn
     * @return
     */
    private static SerializedLambda getSerializedLambda(Serializable fn) {
        //先检查缓存中是否已存在
        SerializedLambda lambda = CLASS_LAMDBA_CACHE.get(fn.getClass());
        if (lambda == null) {
            try {//提取SerializedLambda并缓存
                Method method = fn.getClass().getDeclaredMethod("writeReplace");
                method.setAccessible(Boolean.TRUE);
                lambda = (SerializedLambda) method.invoke(fn);
                CLASS_LAMDBA_CACHE.put(fn.getClass(), lambda);
            } catch (Exception e) {
                log.error("获取SerializedLambda异常, class=" + fn.getClass().getSimpleName(), e);
            }
        }
        return lambda;
    }
}